package com.ccteam.fluidmusic.view.base

import android.view.View
import android.widget.EdgeEffect
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.recyclerview.widget.RecyclerView
import java.lang.ref.WeakReference
import javax.inject.Inject

/**
 * @author Xiaoc
 * @since 2021/2/2
 *
 * RecyclerView的回弹效果工厂
 * 该类继承 [RecyclerView.EdgeEffectFactory] ，它是一个过度边缘滚动的效果的工厂
 * 我们需要重写 [createEdgeEffect] 方法，返回一个 [EdgeEffect] 边缘效果的对象
 *
 * 我们这里不保留RecyclerView原始效果，仅加入了回弹效果，但它与 BaseBounceEdgeEffectFactory（已抛弃） 的不同之处在于
 * 1. BaseBounceEdgeEffectFactory 是RecyclerView里的组件进行动画，代表着显示有多少内容，就有多少内容执行动画
 * 2. 该类是基于传递过来的View进而进行动画的，因为它仅用于一个View的动画，性能会比 BaseBounceEdgeEffectFactory 好一些
 *
 * 使用该工厂有以下注意点：
 * 暂不支持水平回弹
 *
**/
class LightBounceEdgeEffectFactory @Inject constructor(): RecyclerView.EdgeEffectFactory(), LifecycleObserver {

    private var bounceView: WeakReference<View>? = null

    private var bounceAnimation: SpringAnimation? = null

    companion object{

        /** 缓冲值，拉动时的比例系数 */
        private const val OVER_SCROLL_TRANSLATION_MAGNITUDE = 0.3f

        /** 到边缘时弹动的比例系数 */
        private const val FLING_TRANSLATION_MAGNITUDE = 0.45f
    }

    /**
     * 设置需要应用回弹的View控件
     */
    fun applyBounceView(view: View,lifecycleOwner: LifecycleOwner){
        lifecycleOwner.lifecycle.addObserver(this)
        clearView()
        bounceView = WeakReference(view)
        bounceAnimation = SpringAnimation(view,SpringAnimation.TRANSLATION_Y)
            .setSpring(
                SpringForce()
                    .setFinalPosition(0f)
                    .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
                    .setStiffness(SpringForce.STIFFNESS_LOW)
            )

    }

    /**
     * 返回一个 [EdgeEffect] 边缘效果的对象
     * 我们这里既保留了RecyclerView原始效果，同时加入了回弹效果，所以在其 [EdgeEffect] 基础上进行修改
     * @param direction 边缘方向 值为 DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM
     */
    override fun createEdgeEffect(recyclerView: RecyclerView, direction: Int): EdgeEffect {
        return object: EdgeEffect(recyclerView.context){

            /**
             * 在用户朝远离边缘的方向拉动的时候调用
             */
            override fun onPull(deltaDistance: Float) {
                handlePull(deltaDistance)
            }

            /**
             * 在用户朝远离边缘的方向拉动的时候调用
             */
            override fun onPull(deltaDistance: Float, displacement: Float) {
                handlePull(deltaDistance)
            }

            /**
             * 处理拉动效果
             * 将RecyclerView设置translationY属性，让RecyclerView移动 deltaDistance 距离
             * @param deltaDistance 即移动的距离
             *
             * 如果是上拉，则sign为负，移动的Y轴极为向上移动，反之亦然
             * 这里还有一个 OVER_SCROLL_TRANSLATION_MAGNITUDE 为缓冲值，相当于一个比例系数
             */
            private fun handlePull(deltaDistance: Float){
                val sign = if (direction == DIRECTION_BOTTOM) -1 else 1
                val translationYDelta =
                    sign * recyclerView.width * deltaDistance * OVER_SCROLL_TRANSLATION_MAGNITUDE

                bounceView?.get()?.let {
                    bounceAnimation?.cancel()
                    it.translationY += translationYDelta
                }
            }

            /**
             * 当用户放开的手指的那一刻
             * 我们需要在这里让recyclerView的每一个可见item的translationY值变成0
             * 这里使用 SpringAnimation 动画去模拟返回到原始位置，可以随意调整
             */
            override fun onRelease() {
                bounceAnimation?.start()
            }

            /**
             * recyclerView在脱离用户手指滑动期间滚动，但到了recyclerview的边缘时速度不为0会调用此方法
             * @param velocity 力度
             *
             * 我们刚好可以利用SpringAnimation设置对应力度，来达到弹性力度的动画效果
             * 这里有一个 FLING_TRANSLATION_MAGNITUDE 即：到边缘时弹动的比例系数，可以随意调整
             */
            override fun onAbsorb(velocity: Int) {
                val sign = if (direction == DIRECTION_BOTTOM) -1 else 1

                val translationVelocity = sign * velocity * FLING_TRANSLATION_MAGNITUDE

                bounceAnimation?.setStartVelocity(translationVelocity)?.start()
            }
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun clearView(){
        bounceAnimation?.cancel()
        bounceAnimation = null
        bounceView?.clear()
        bounceView = null
    }

}